package com.zkh.myutils.markup;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import com.zkh.myutils.io.file.FileUtils;
import com.zkh.myutils.utils.Assert;
import com.zkh.myutils.utils.StringUtils;
import com.zkh.myutils.utils.Utils;

/**
 * 标记语言解析器
 */
public class MarkupParser {
	/**
	 * 解析属性
	 */
	private static final int PARSE_ATTR = 1;
	/**
	 * 解析xml声明
	 */
	private static final int PARSE_ANNO = 2;
	/**
	 * 解析CDATA
	 */
	private static final int PARSE_CDATA = 3;
	/**
	 * 解析文档类型
	 */
	private static final int PARSE_DOC_TYPE = 4;
	/**
	 * 解析注释
	 */
	private static final int PARSE_COMMENT = 5;
	
	/**
	 * 无内容体的标签（只对doctype为html的文档有效）
	 */
	private final static List<String> NO_BODY_TAG_LIST = Arrays.asList("area", "base", "br", "col", "command", "embed",
		"hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr");
	/**
	 * 不需要深入解析的标签（只对doctype为html的文档有效）
	 */
 	private static final List<String> NO_PARSE_TAG_LIST = Arrays.asList("style", "script");
 	
	/**
	 * 解析文件
	 * @param filepath 待解析的文件路径
	 * @return 解析后的元素对象
	 */
	public static Element parseFile(String filepath) {
		return parseFile(new File(filepath));
	}
	
	/**
	 * 解析文件
	 * @param file 待解析的文件
	 * @return 解析后的元素对象
	 */
	public static Element parseFile(File file) {
		//解析文本
		return parseText(FileUtils.readAll(file.getAbsolutePath()));
	}
	
	/**
	 * 解析流
	 * @param markupInputStream 待解析的流
	 * @return 解析后的元素对象
	 */
	public static Element parseInputStream(InputStream markupInputStream) {
		//解析文本
		return parseText(FileUtils.readAll(markupInputStream));
	}
	
	/**
	 * 解析文本
	 * @param text 待解析的文本
	 * @return 解析后的元素对象
	 */
	public static Element parseText(String text) {
		Objects.requireNonNull(text);
		//为空
		if(text.trim().isEmpty()) {
			throw new MarkupException("正文内容不能为空");
		}
		//html文档
		boolean isHtml = false;
		//解析后的对象
		DocElement element = new DocElement();
		/* 解析过程需要用到的临时变量 */
		//当前状态，上一状态（CDATA和注释解析结束后，需要恢复原来状态），当前元素，上一字符，解析特殊字符中标识
		int state=0, lastState=0; Element curr=element; char lastChar='\0'; boolean parseSpecial=false;
		//标签结束标识
		boolean tagEnd = false;
		//缓存内容，当前行缓存内容
		LinkedList<Character> allCharList=new LinkedList<>(), tempCharList=new LinkedList<>();
		int l;
		//转字符数组
		char[] chars = text.toCharArray();
		//遍历解析
		outer: for(int i=0,len=chars.length; i<len; i++) {
			//当前字符
			char c = chars[i];
			//识别新行
			if(c=='\r' || c=='\n') {
				//\r\n的情况直接跳过
				if(c!='\n' || lastChar!='\r') {
					//清除行字符缓存
					lastChar='\n';
				}
				//追加当前字符
				allCharList.add(c);
				//继续下次循环
				continue;
			}
			//追加当前字符
			allCharList.add(c);
			/* 根据当前状态解析各部分 */
			//解析属性
			if(state==PARSE_ATTR) {
				//清空字符
				allCharList.clear();
				//遍历
				for(; i<len; i++) {
					//当前字符
					allCharList.add(c=chars[i]);
					//如果是字符串
					if(c=='"' || c=='\'') {
						//查找字符串结束
						int end = findStringEnd(chars, i, c);
						//未找到
						if(end==-1) {
							throw new MarkupException("解析失败，在字符“" + Utils.join("", allCharList) + "”附近，属性值无结束引号");
						}
						for(char cc: Arrays.copyOfRange(chars, i+1, end+1)) {
							allCharList.add(cc);
						}
						//移动索引
						i = end;
					}
					//结束
					else if(c=='/' || c=='>') {
						//移除当前字符
						allCharList.removeLast();
						//直接结束
						if(c=='/') {
							if(i+1>=len || chars[i+1]!='>') {
								throw new MarkupException("无标签体的标签必须以/>结尾");
							}
							curr.setNoTagBody(true);
							//结束标识
							tagEnd = true;
							//将位置移到>符号
							i++;
						}//上一字符
						lastChar = '>';
						//解析属性并保存
						curr.putAttrs(parseAttr(allCharList));
						//HTML无标签体的标签，如果没有结束符/>，则自动补全
						if(isHtml && NO_BODY_TAG_LIST.contains(curr.getName().toLowerCase()) && !tagEnd) {
							tagEnd = true; curr.setNoTagBody(true);
						}
						//清除缓存
						allCharList.clear();
						//不解析的标签
						if(isHtml && !tagEnd && NO_PARSE_TAG_LIST.contains(curr.getName().toLowerCase())) {
							//结束标签
							char[] endTag = ("</" + curr.getName() + ">").toCharArray();
							//查找结束位置
							int end = findTagEnd(chars, i, endTag);
							//保存内容
							curr.setText(copyString(chars, i+1, end-endTag.length+1));
							//保存新索引
							i = end;
							//保存状态
							tagEnd = true;
						}
						//重置状态（开始标签已解析完，禁止回退到state，上一状态保存为0）
						lastState=0; state=0;
						continue outer;
					}
				}
			}//解析xml声明（必须以?>结尾）
			else if(state==PARSE_ANNO) {
				//清空字符
				allCharList.clear();
				//遍历
				for(; i<len; i++) {
					//当前字符
					allCharList.add(c=chars[i]);
					//结尾
					if(c=='>') {
						if((l=allCharList.size())>1 && allCharList.get(l-2)!='?') {
							throw new MarkupException("xml声明必须以?>结束");
						}
						//当前应该是根节点
						if(curr instanceof DocElement) {
							DocElement doc = (DocElement) curr;
							//移除结束符号?>并处理属性
							LinkedHashMap<String, String> map = parseAttr(allCharList.subList(0, l-2));
							//基本属性
							Assert.doIfNotNull(map.get("version"), doc::setXmlVersion);
							Assert.doIfNotNull(map.get("encoding"), doc::setXmlEncoding);
							Assert.doIfNotNull(map.get("standalone"), t->doc.setXmlStandalone("yes".equalsIgnoreCase(t)));
							//上一字符
							lastChar = c;
							//结束，重置状态，并继续后续循环
							lastState = state; state = 0;
							continue outer;
						}else {
							throw new MarkupException("xml声明必须位于顶级标签之前");
						}
					}
				}
			}//CDATA块 ]]>
			else if(state==PARSE_CDATA) {
				//清除重复字符
				allCharList.removeLast();
				//CDATA需要原样展示
				allCharList.addAll(Arrays.asList('C', 'D', 'A', 'T', 'A', '['));
				//结束标识
				char[] endStr = {']', ']', '>'};
				//遍历
				for(; i<len; i++) {
					//当前字符
					allCharList.add(c=chars[i]);
					//判断结束
					if(endWith(allCharList, endStr)) {
						//上一字符
						lastChar = c;
						//还原状态
						state = lastState; lastState = 0;
						//跳出循环
						continue outer;
					}
				}
			}//文档类型
			else if(state==PARSE_DOC_TYPE) {
				//清空字符
				allCharList.clear();
				//遍历
				for(; i<len; i++) {
					//当前字符
					allCharList.add(c = chars[i]);
					//结尾
					if(c=='>') {
						//当前应该是根节点
						if(curr instanceof DocElement) {
							DocElement doc = (DocElement) curr;
							//移除最后的>
							allCharList.removeLast();
							//文档类型容器
							List<String> docTypeList = new ArrayList<>();
							List<Character> wordList = new ArrayList<>();
							//上一字符
							lastChar = '\0';
							//处理
							for(int j=0,jlen=allCharList.size(); j<jlen; j++) {
								//当前字符
								char t = allCharList.get(j);
								//当前字符为空
								if(t<=' ') {
									//上一字符不为空，保存内容
									if(lastChar>' ' && !wordList.isEmpty()) {
										docTypeList.add(Utils.join("", wordList));
										//重置
										wordList.clear();
									}
								}//引号
								else if(t=='"' || t=='\'') {
									//查找字符串结束
									int end = findStringEnd(allCharList, j, t);
									//保存内容
									docTypeList.add(Utils.join("", allCharList.subList(j+1, end)));
									//更新索引
									j = end;
								}else {
									wordList.add(t);
								}
								//上一字符
								lastChar = t;
							}
							//还有内容，再次保存
							if(!wordList.isEmpty()) {
								docTypeList.add(Utils.join("", wordList));
							}
							//保存属性
							doc.setDocType(docTypeList.get(0));
							//是否是html
							isHtml = "html".equalsIgnoreCase(doc.getDocType());
							//PUBLIC
							if(docTypeList.size()>3) {
								if(!"PUBLIC".equalsIgnoreCase(docTypeList.get(1))) {
									throw new MarkupException("文档声明格式错误，正确格式为：<!DOCTYPE 类型[ PUBLIC \"PUBLIC值\"[ \"SystemId值\"]] >");
								}
								doc.setPublicId(docTypeList.get(2));
							}
							//SystemId
							Assert.doIfTrue(docTypeList.size()>=4, ()->doc.setSystemId(docTypeList.get(3)));
							//上一字符
							lastChar = c;
							//结束，重置状态，并继续后续循环
							lastState = state; state = 0;
							continue outer;
						}else {
							throw new MarkupException("文档类型声明必须位于顶级标签之前");
						}
					}
				}
			}
			//解析注释，结束标识（-->）
			else if(state==PARSE_COMMENT) {
				//清除注释开头字符
				allCharList.removeLast();
				allCharList.removeLast();
				allCharList.removeLast();
				allCharList.removeLast();
				//清空临时容器
				tempCharList.clear();
				//结束标识
				char[] endStr = {'-', '-', '>'};
				//遍历
				for(; i<len; i++) {
					//当前字符
					tempCharList.add(c = chars[i]);
					//判断结束
					if(endWith(tempCharList, endStr)) {
						//结束
						state = lastState; lastState = 0;
						//上一字符
						lastChar = c;
						//清空字符
						tempCharList.clear();
						//跳出循环
						continue outer;
					}
				}
			}

			//检查特殊字符
			if(lastChar=='<') {
				//空字符
				if(c<=' ') {
					throw new MarkupException("解析异常，“<”符后面不能为空或换行");
				}
				//特殊字符
				if(c=='!' || c=='?' || c=='/') {
					//已经是特殊字符
					if(parseSpecial) {
						throw new MarkupException("解析异常，字符：<" + c);
					}
					//标记特殊字符
					parseSpecial = true;
				}//正常开始标签
				else {
					allCharList.removeLast();
					allCharList.removeLast();
					//如果有文本，处理文本节点
					String txt = Utils.join("", allCharList);
					//标签名称
					StringBuilder name = new StringBuilder();
					//结束标识
					boolean attrEnd = false, tagEndInd = false;
					//无标签标识
					boolean noTagBody = false;
					//不是空格或没有结束
					while(c>' ' && i<len) {
						//直接结束，如html的<br/>标签
						if(c=='/') {
							//检查下一字符必须为>
							if(i+1>=len || chars[i+1]!='>') {
								throw new MarkupException("解析异常，无标签体的标签必须以“/>”结束，在字符“" + name + "”附近");
							}
							//移动索引
							c = chars[++i]; attrEnd = true; tagEndInd = true; curr.setNoTagBody(noTagBody = true);
							break;
						}
						if(c=='>') {
							attrEnd = true; break;
						}
						//保存字符
						name.append(c);
						//移动索引
						c = chars[++i];
					}
					//获取当前临时元素
					Element temp = curr;
					//标签名称为空，说明是根元素
					if(StringUtils.isEmpty(temp.getName())) {
						temp.setName(name.toString());
					}else {
						//HTML无标签体的标签，如果没有结束符/>，则自动补全
						if(isHtml && NO_BODY_TAG_LIST.contains(name.toString().toLowerCase()) && attrEnd && !tagEndInd) {
							tagEndInd = true; curr.setNoTagBody(noTagBody = true);
						}
						//新建元素并保存父元素（如果前面标签已结束，说明是兄弟节点）
						(curr = new Element(name.toString()).setNoTagBody(noTagBody)).setParent(tagEnd?temp.getParent():temp);
						//获取父节点
						Element p = curr.getParent();
						//给父元素追加子元素
						p.addChild(curr);
						//如果父元素无标签体标识为true（当前已经属于标签体）
						if(p.isNoTagBody()) {
							p.setNoTagBody(false);
						}
						//有文本节点
						if(!txt.trim().isEmpty()) {
							//文本元素
							Element txtElem = new Element(Element.ELEMENT_TEXT);
							txtElem.setText(txt);
							txtElem.setParent(curr.getParent());
							//插入
							curr.addBefore(txtElem);
						}
					}
					//属性结束，清空缓存
					if(attrEnd) {
						allCharList.clear();
					}
					lastState = state;
					//当前步骤
					state = attrEnd ? 0 : PARSE_ATTR;
					//不解析的标签
					if(attrEnd && isHtml && !tagEndInd && NO_PARSE_TAG_LIST.contains(curr.getName().toLowerCase())) {
						//结束标签
						char[] endTag = ("</" + curr.getName() + ">").toCharArray();
						//查找结束位置
						int end = findTagEnd(chars, i, endTag);
						//保存内容
						curr.setText(copyString(chars, i+1, end-endTag.length+1));
						//保存新索引
						i = end;
						//保存状态
						tagEndInd = true;
					}
					//保存标签是否结束标识
					tagEnd = tagEndInd;
				}
				//上个字符
				lastChar = c;
				//跳过
				continue;
			}
			//解析特殊字符，确认当前解析状态
			if(parseSpecial) {
				parseSpecial = false;
				//xml声明（<?xml）
				if(c=='x' && lastChar=='?') {
					//检查注释
					i = checkSpecial(i, 2, "xml", chars, false);
					//没有结束，且没有空格
					if(i<len && chars[++i]>' ') {
						throw new MarkupException("解析异常，字符串：<?xml" + chars[i]);
					}
					//检查状态
					if(state>0) {
						throw new MarkupException("格式错误");
					}
					//保存状态
					state = PARSE_ANNO;
				}//注释（<!--）
				else if(c=='-' && lastChar=='!') {
					//检查注释
					i = checkSpecial(i, 1, "--", chars, false);
					//保存状态
					lastState = state; state = PARSE_COMMENT;
				}//CDATA（<![CDATA[）
				else if(c=='[' && lastChar=='!') {
					//检查CDATA
					i = checkSpecial(i, 6, "[CDATA[", chars, false);
					//保存状态
					lastState = state; state = PARSE_CDATA;
				}//文档类型（<!DOCTYPE）
				else if((c=='D' || c=='d') && lastChar=='!') {
					//检查文档类型
					i = checkSpecial(i, 6, "DOCTYPE", chars, true);
					//没有结束，且没有空格
					if(i<len && chars[++i]>' ') {
						throw new MarkupException("解析异常，字符串：<?DOCTYPE" + chars[i]);
					}
					//保存状态
					state = PARSE_DOC_TYPE;
				}//结束标签（如果标签内含子标签，则标签内的文本解析为文本节点，否则解析为标签体文本内容）
				else if(lastChar=='/'){
					//移除</
					allCharList.removeLast();
					allCharList.removeLast();
					allCharList.removeLast();
					//获取标签体
					String body = Utils.join("", allCharList);
					//获取结束符号
					int end = findChar(chars, '>', i);
					//未找到结束标识
					if(end==-1) {
						throw new MarkupException("结束标签错误");
					}
					//获取结束标签名称
					String endTagName = copyString(chars, i, end);
					//更新索引
					i = end;
					//当前标签已完成，如果前面标签已完成，则标签上移
					if(tagEnd) {
						curr = curr.getParent();
					}
					//检查结束标签含空格
					if(!endTagName.equals(endTagName.trim())) {
						throw new MarkupException("结束标签不能包含空字符：【" + endTagName + "】");
					}
					//检查开始标签和结束标签是否相同
					if(!curr.getName().equalsIgnoreCase(endTagName)) {
						throw new MarkupException("开始标签和结束标签不匹配：" + curr.getName() + "->" + endTagName);
					}
					//保存标签体内容
					if(!body.trim().isEmpty()) {
						//有子节点，解析为文本节点，否则解析为文本内容
						if(curr.hasChild()) {
							//文本元素
							Element txtElem = new Element(Element.ELEMENT_TEXT);
							txtElem.setText(body);
							txtElem.setParent(curr.getParent());
							//插入
							curr.addChild(txtElem);
						}else {
							curr.setText(body);
						}
					}
					//清空缓存
					allCharList.clear();
					//解析完成，重置解析状态
					lastState = state; state = 0;
					//保存结束标识
					tagEnd = true;
				}else {
					throw new MarkupException("解析异常，字符：" + c);
				}
			}
			//处理完成，将当前字符设置为上次字符
			lastChar = c;
		}
		//为空
		if(element.getName()==null) {
			throw new MarkupException("正文内容至少需要一个根节点元素");
		}
		//返回
		return element;
	}
	
	/**
	 * 解析属性
	 * @param charList 属性字符串
	 * @return 解析的属性Map
	 */
	private static LinkedHashMap<String, String> parseAttr(List<Character> charList){
		//容器
		 LinkedHashMap<String, String> map = new LinkedHashMap<>();
		 //临时字符
		 StringBuilder sb = new StringBuilder();
		 //上一字符
		 char lastChar = '\0';
		 //属性值无引号
		 boolean noQuote = false;
		 //遍历
		 for(int i=0,len=charList.size(); i<len; i++) {
			 //当前字符
			 char c = charList.get(i);
			 //前面是=
			 if(lastChar=='=') {
				 //现在是空格
				 if(c<=' ') {
					 continue;
				 }
				 //属性值无引号
				 if(c!='"' && c!='\'') {
					 //无引号属性值
					 noQuote = true;
					 //找空格
					 while(i<len) {
						 //获取当前字符
						 c = charList.get(i);
						 //空格
						 if(c<=' ') {
							 //上一字符
							 lastChar = c;
							 break;
						 }
						 //后移
						 i++;
						 //保存当前字符
						 sb.append(c);
					 }
				 }else {
					 //属性值有引号
					 noQuote = false;
					 //解析字符串
					 int end = findStringEnd(charList, i, c);
					 //未结束
					 if(end==-1) {
						 throw new MarkupException("属性解析失败，未找到结束字符：" + Utils.join("", charList));
					 }
					 //保存解析的字符串
					 sb.append(Utils.join("", charList.subList(i,  end+1)));
					 //移动索引
					 i = end;
					 //上一字符标识
					 lastChar = c;
				 }
				 continue;
			 }
			 //前面是空格
			 if(lastChar==' ') {
				 //现在也是空格
				 if(c<=' ') {
					 continue;
				 }
				 //不是=，开始处理属性
				 if(c!='=') {
					 //解析属性并保存
					 saveAttr(map, sb.toString(), noQuote);
					 //清除容器
					 sb.setLength(0);
				 }
			 }
			 //当前不是空格，则保存当前字符
			 if(c>' ') {
				 sb.append(c);
			 }
			 //保存上一字符
			 lastChar = c;
		 }
		 //有缓存值，解析属性并保存
		 if(sb.length()>0) {
			 saveAttr(map, sb.toString(), noQuote);
		 }
		 //返回
		 return map;
	}
	
	/**
	 * 保存属性
	 * @param map 属性容器
	 * @param attrString 属性字符
	 * @param noQuote 属性值无引号
	 */
	private static void saveAttr(Map<String, String> map, String attrString, boolean noQuote) {
		//分隔符=
		int sep = attrString.indexOf('=');
		//存在
		if(sep>-1) {
			//截取位置
			int start = sep+2, end = attrString.length()-1;
			//属性值无引号
			if(noQuote) {
				start--; end++;
			}
			//按=拆分，但值要去掉引号
			map.put(attrString.substring(0, sep), attrString.substring(start, end));
		}else {
			map.put(attrString, null);
		}
	}
	
	/**
	 * 查询字符串结束索引
	 * @param charList 查找内容
	 * @param start 开始索引
	 * @param startChar 开始字符
	 * @return 字符串结束索引
	 */
	private static int findStringEnd(List<Character> charList, int start, char startChar) {
		return findStringEnd(null, charList, start, startChar);
	}
	
	/**
	 * 查询字符串结束索引
	 * @param charArr 查找内容
	 * @param start 开始索引
	 * @param startChar 开始字符
	 * @return 字符串结束索引
	 */
	private static int findStringEnd(char[] charArr, int start, char startChar) {
		return findStringEnd(charArr, null, start, startChar);
	}
	
	/**
	 * 查询字符串结束索引
	 * @param charArr 查找内容
	 * @param charList 查找内容
	 * @param start 开始索引
	 * @param startChar 开始字符
	 * @return 字符串结束索引
	 */
	private static int findStringEnd(char[] charArr, List<Character> charList, int start, char startChar) {
		//转义符号数量
		int escape = 0;
		//入参属性
		boolean isArr = charArr!=null;
		int len = isArr ? charArr.length : charList.size();
		//遍历
		for(int i=start+1; i<len; i++){
			//当前字符
			char c = isArr ? charArr[i] : charList.get(i);
			//检测到引号（当引号前面存在奇数个转义符号时，引号要当作普通字符）
			if(c==startChar && escape%2==0) {
				//返回索引
				return i;
			}else {
				escape = c=='\\' ? escape + 1 : 0;
			}
		}
		//未找到结束字符
		return -1;
	}
	
	/**
	 * 查询字符
	 * @param chars 待查找字符
	 * @param c 要查找的字符
	 * @param from 开始查找的位置
	 * @return 查找到的字符索引
	 */
	private static int findChar(char[] chars, char c, int from) {
		for(int i=from, len=chars.length; i<len; i++) {
			if(chars[i]==c) {
				return i;
			}
		}
		return -1;
	}
	
	/**
	 * 截取字符并组装为String
	 * @param chars 待处理的字符数组
	 * @param from 开始索引
	 * @param to 结束索引
	 * @return 截取后的字符串
	 */
	private static String copyString(char[] chars, int from, int to) {
		return new String(Arrays.copyOfRange(chars, from, to));
	}
	
	/**
	 * 判断集合是否以指定字符结尾
	 * @param list 集合
	 * @param endStr 结尾字符
	 */
	private static boolean endWith(List<Character> list, char[] endStr) {
		int len1 = list.size(), len2 = endStr.length;
		//长度不足，返回false
		if(len1<len2) {
			return false;
		}
		for(int i=len2-1; i>=0; i--) {
			//不相等（不区分大小写）
			if(Character.toLowerCase(list.get(len1-len2+i))!=Character.toLowerCase(endStr[i])) {
				return false;
			}
		}
		//相等
		return true;
	}
	
	
	/**
	 * 检查特殊类型
	 * @param i 当前字符索引
	 * @param offset 偏移数量
	 * @param checkString 要对比的字符串
	 * @param line 行号
	 * @param content 该行内容
	 * @return 位移后的新索引位置
	 */
	/**
	 * 检查特殊类型
	 * @param i 当前字符索引
	 * @param offset 偏移数量
	 * @param checkString 要对比的字符串
	 * @param chars 待检查字符数组
	 * @param ignoreCase 是否忽略大小写
	 */
	private static int checkSpecial(int i, int offset, String checkString, char[] chars, boolean ignoreCase) {
		//临时字符
		String temp = null;
		//新索引位置
		int index = i + offset;
		//超过位置
		if(index>=chars.length) {
			throw new MarkupException("错误的格式：<!" + copyString(chars, i, chars.length));
		}
		//保存临时字符
		temp = copyString(chars, i, index+1);
		//检查格式
		if(ignoreCase && !checkString.equalsIgnoreCase(temp) || !ignoreCase && !checkString.equals(temp)) {
			throw new MarkupException("错误的格式：<?" + temp);
		}
		//返回位置
		return index;
	}
	
	/**
	 * 查找标签结束位置
	 * @param charArr 待查询字符
	 * @param start 开始位置
	 * @param endTagChars 结束标签
	 * @return 标签结束位置
	 */
	private static int findTagEnd(char[] charArr, int start, char[] endTagChars) {
		//临时内容
		List<Character> tempList = new ArrayList<>();
		int i=start+1,len=charArr.length;
		//遍历
		while(i<len) {
			//当前字符
			tempList.add(charArr[i++]);
			//判断结束
			if(endWith(tempList, endTagChars)) {
				return i-1;
			}
		}
		//抛出异常
		throw new MarkupException("未找到结束标签：" + new String(endTagChars));
	}
}
